package com.qinker.robot.hook;

import android.app.Application;
import android.content.Context;

import com.alibaba.fastjson.JSON;
import com.sky.xposed.common.BuildConfig;
import com.sky.xposed.common.util.Alog;

import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

/**
 * 调试监控HOOK
 * 打印出指定apk的所有堆栈信息
 * Created by Irany 2019/10/19 0019 23:11
 */
public class MonitorMain implements IXposedHookLoadPackage {

    private static Map<String, Object> hookClassList = new ConcurrentHashMap<>();


    private AtomicInteger countClass = new AtomicInteger(2000);

    private AtomicBoolean stopScanClass = new AtomicBoolean();


    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws InterruptedException {


        // 被Hook操作的目标Android应用的包名，进行Hook操作的过滤
        Alog.setDebug(true);

        if (!StringUtils.equals(Constant.Rimet.PACKAGE_NAME, lpparam.packageName)
                || !StringUtils.equals(Constant.Rimet.PACKAGE_NAME, lpparam.processName)) {
            return;
        }


        Alog.i("Loaded App:" + lpparam.packageName);

        XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() {

            // 在类方法loadClass执行之后执行的代码
            @Override
            protected void afterHookedMethod(MethodHookParam param) {
                if (countClass.addAndGet(-1) < 0) {
                    countClass.set(0);
                    stopScanClass.getAndSet(true);
                    return;
                }

                if (stopScanClass.get()) {
                    return;
                }
                // 参数的检查
                if (param.hasThrowable()) {
                    return;
                }

                // 获取指定名称的类加载之后的Class<?>
                Class<?> clazz = (Class<?>) param.getResult();
                // 获取加载的指定类的名称
                String strClazz = clazz.getName();

                if (filterClass(strClazz)) {

                    hookClassList.put(strClazz, Object.class);
                    countClass.getAndDecrement();
                    Alog.i(hookClassList.size() + "inc countDownLatch: " + countClass.get());


                }
            }
        });

        //因为有多个dex,在ClassLoader加载类的时候会有其他dex还没加载到的请,所以在Application.attach进行hook
        XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                while (countClass.get() >= 0 && !stopScanClass.get()) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Alog.i("countClass: " + countClass.get() + ",stopScanClass: " + stopScanClass.get() + ",classSize: " + hookClassList.size());
                }
                Alog.i("开始HOOK方法...");
                for (String strClazz : hookClassList.keySet()) {
                    ClassLoader cl = ((Context) param.args[0]).getClassLoader();
                    Class<?> clazz = cl.loadClass(strClazz);

                    Alog.i("LoadClass : " + strClazz);
                    //XposedBridge.log("HookedClass : "+strClazz);
                    // 获取到指定名称类声明的所有方法的信息
                    Method[] m = clazz.getDeclaredMethods();
                    // 打印获取到的所有的类方法的信息
                    for (Method method : m) {
                        Alog.i("LoadMethod: " + clazz.getName() + " - " + method.getName() + "@" + method.getParameterTypes().length);
                        //XposedBridge.log("HOOKED CLASS-METHOD: "+strClazz+"-"+m[i].toString());
                        if (filterMethod(method)) {

                            // 对指定名称类中声明的非抽象方法进行java Hook处理
                            XposedBridge.hookMethod(method, new XC_MethodHook() {

                                // 被java Hook的类方法执行完毕之后，打印log日志
                                @Override
                                protected void afterHookedMethod(MethodHookParam param) throws Throwable {

                                    // 打印被java Hook的类方法的名称和参数类型等信息
                                    String methodInfo = strClazz + "-" + param.method.toString();

                                    /*String result = String.valueOf(param.getResult());
                                    if (result != null) {
                                        //LauncherApplication在转JSON的时候会导致内存泄漏,务必要过滤掉
                                        if (!result.contains("com.alibaba.android.rimet.LauncherApplication")) {
                                            result = JSON.toJSONString(param.getResult());
                                        }
                                    }*/
                                    if (param.args != null) {
                                        for (int i = 0; i < param.args.length; i++) {
                                            Alog.i("HOOKED PARAM,第" + (i + 1) + "个参数: " + param.args[i]);
                                        }
                                    }

                                    //Alog.i("HOOKED METHOD: " + methodInfo + ",返回值: " + result);
                                    Alog.i("Xposed", "堆栈: ", new Exception());
                                }
                            });
                        }
                    }
                }
            }
        });
    }


    private boolean filterMethod(Method method) {
        // 过滤掉指定名称类中声明的抽象方法
        return !Modifier.isAbstract(method.getModifiers())
                // 过滤掉指定名称类中声明的Native方法
                && !Modifier.isNative(method.getModifiers())
                // 过滤掉指定名称类中声明的接口方法
                && !Modifier.isInterface(method.getModifiers())
                && !("decode".equals(method.getName()) && method.getParameterTypes().length == 2)
                && !("equals".equals(method.getName()) && method.getParameterTypes().length == 2)
                && !("clone".equals(method.getName()) && method.getParameterTypes().length == 2);
    }

    private boolean filterClass(String strClazz) {
        return (strClazz.contains("com.alibaba.android"))
                && !strClazz.contains("com.alibaba.android.note.base.interfaces.NoteInterface")
                && !strClazz.contains("com.alibaba.android.dingtalkbase.multidexsupport.DDApplication.getApplicationContext")
                && !strClazz.contains("com.alibaba.android.rimet.LauncherApplication")
                && !strClazz.contains("com.alibaba.android.dingtalkbase.multidexsupport.DDContext")
                && !strClazz.contains("com.alibaba.android.dingtalkim.session.SessionFragmentImplV2");
    }

}
